"use client"; import { useEffect, useRef, useState } from "react"; import { fmtDateTime } from "@/lib/format"; type Ev = { seq: number; at: string; level: string; kind: string; stage?: string | null; message: string; data?: unknown; }; export function LiveTimeline({ runId, initial, active }: { runId: string; initial: Ev[]; active: boolean }) { const [events, setEvents] = useState(initial); const [state, setState] = useState<"idle" | "live" | "reconnecting" | "closed">( active ? "reconnecting" : "closed", ); const lastSeq = useRef(initial.at(-1)?.seq ?? 0); const bottom = useRef(null); const [follow, setFollow] = useState(true); useEffect(() => { if (!active) return; let es: EventSource | null = null; let stopped = false; let retry = 1000; const connect = () => { if (stopped) return; es = new EventSource(`/api/admin/runs/${runId}/events?stream=1&after=${lastSeq.current}`); es.addEventListener("hello", () => { setState("live"); retry = 1000; }); es.addEventListener("run.event", (m) => { const ev = JSON.parse((m as MessageEvent).data) as Ev; if (ev.seq <= lastSeq.current) return; lastSeq.current = ev.seq; setEvents((x) => [...x, ev]); if (ev.kind === "status.transition" && /→ (published|failed|cancelled)/.test(ev.message)) { stopped = true; es?.close(); setState("closed"); setTimeout(() => window.location.reload(), 1500); } }); es.onerror = () => { es?.close(); setState("reconnecting"); if (!stopped) { retry = Math.min(retry * 2, 15000); setTimeout(connect, retry); } }; }; connect(); return () => { stopped = true; es?.close(); }; }, [runId, active]); useEffect(() => { if (follow) bottom.current?.scrollIntoView({ block: "nearest" }); }, [follow]); return (
{state === "live" ? ( ● live ) : state === "reconnecting" ? ( ● reconnecting… ) : ( ● stream closed )}{" "} · {events.length} events · cursor {lastSeq.current}
    {events.length === 0 &&
  1. No events yet.
  2. } {events.map((e) => (
  3. {e.seq} {fmtDateTime(e.at)} {e.stage ?? ""} {e.kind} {e.message} {e.data && Object.keys(e.data as object).length > 0 ? (
    ⋯
    {JSON.stringify(e.data, null, 1)}
    ) : null}
  4. ))}
); }